package com.tsfyun.scm.service.impl.finance;

import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.lang.Snowflake;
import cn.hutool.core.util.StrUtil;
import com.github.pagehelper.PageHelper;
import com.github.pagehelper.PageInfo;
import com.google.common.collect.ImmutableMap;
import com.google.common.collect.Lists;
import com.google.common.collect.Maps;
import com.tsfyun.common.base.dto.*;
import com.tsfyun.common.base.enums.*;
import com.tsfyun.common.base.enums.domain.DomainOprationEnum;
import com.tsfyun.common.base.enums.domain.DomainTypeEnum;
import com.tsfyun.common.base.enums.domain.ImpOrderStatusEnum;
import com.tsfyun.common.base.enums.domain.PaymentAccountStatusEnum;
import com.tsfyun.common.base.enums.finance.*;
import com.tsfyun.common.base.enums.risk.RiskApprovalDocTypeEnum;
import com.tsfyun.common.base.exception.ServiceException;
import com.tsfyun.common.base.security.SecurityUtil;
import com.tsfyun.common.base.support.DomainStatus;
import com.tsfyun.common.base.util.DateUtils;
import com.tsfyun.common.base.util.LocalDateTimeUtils;
import com.tsfyun.common.base.util.StringUtils;
import com.tsfyun.common.base.util.TsfPreconditions;
import com.tsfyun.scm.base.DataCalling;
import com.tsfyun.scm.dto.finance.*;
import com.tsfyun.scm.dto.support.TaskNoticeContentDTO;
import com.tsfyun.scm.entity.base.Currency;
import com.tsfyun.scm.entity.customer.Customer;
import com.tsfyun.scm.entity.customer.ImpQuote;
import com.tsfyun.scm.entity.finance.PaymentAccount;
import com.tsfyun.scm.entity.finance.PaymentAccountMember;
import com.tsfyun.scm.entity.order.ImpOrder;
import com.tsfyun.scm.entity.order.ImpOrderCost;
import com.tsfyun.scm.entity.risk.RiskApproval;
import com.tsfyun.scm.entity.system.SubjectOverseas;
import com.tsfyun.scm.entity.system.SubjectOverseasBank;
import com.tsfyun.scm.entity.user.Person;
import com.tsfyun.scm.mapper.finance.PaymentAccountMapper;
import com.tsfyun.scm.service.base.ICurrencyService;
import com.tsfyun.scm.service.common.ICommonService;
import com.tsfyun.scm.service.customer.ICustomerService;
import com.tsfyun.scm.service.customer.IImpClauseService;
import com.tsfyun.scm.service.customer.IImpQuoteService;
import com.tsfyun.scm.service.customer.ISupplierBankService;
import com.tsfyun.scm.service.file.IExportFileService;
import com.tsfyun.scm.service.finance.IPaymentAccountMemberService;
import com.tsfyun.scm.service.finance.IPaymentAccountService;
import com.tsfyun.common.base.extension.ServiceImpl;
import com.tsfyun.scm.service.finance.ITransactionFlowService;
import com.tsfyun.scm.service.finance.IWriteOffService;
import com.tsfyun.scm.service.order.IImpOrderCostService;
import com.tsfyun.scm.service.order.IImpOrderPaymentService;
import com.tsfyun.scm.service.order.IImpOrderService;
import com.tsfyun.scm.service.risk.IRiskApprovalService;
import com.tsfyun.scm.service.support.ITaskNoticeContentService;
import com.tsfyun.scm.service.system.ISerialNumberService;
import com.tsfyun.scm.service.system.IStatusHistoryService;
import com.tsfyun.scm.service.system.ISubjectOverseasBankService;
import com.tsfyun.scm.service.system.ISubjectOverseasService;
import com.tsfyun.scm.service.third.IShortMessageService;
import com.tsfyun.scm.service.third.IWxMessageService;
import com.tsfyun.scm.service.user.IPersonService;
import com.tsfyun.scm.stream.send.ScmProcessor;
import com.tsfyun.scm.util.*;
import com.tsfyun.scm.vo.customer.ImpClauseOrderVO;
import com.tsfyun.scm.vo.customer.SupplierBankVO;
import com.tsfyun.scm.vo.finance.*;
import com.tsfyun.scm.vo.finance.client.*;
import com.tsfyun.scm.vo.order.ApplyPaymentPlusVO;
import com.tsfyun.scm.vo.order.ApplyPaymentVO;
import com.tsfyun.scm.vo.order.ImpOrderPaySituationVO;
import com.tsfyun.scm.vo.system.SubjectOverseasVO;
import jxl.Workbook;
import jxl.format.Alignment;
import jxl.format.VerticalAlignment;
import jxl.write.*;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.collections4.map.LinkedMap;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.cloud.context.config.annotation.RefreshScope;
import org.springframework.dao.DuplicateKeyException;
import org.springframework.integration.redis.util.RedisLockRegistry;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import tk.mybatis.mapper.entity.Example;

import javax.annotation.Resource;
import java.io.File;
import java.lang.Boolean;
import java.math.BigDecimal;
import java.time.LocalDateTime;
import java.util.*;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.locks.Lock;
import java.util.function.Function;
import java.util.stream.Collectors;

/**
 * <p>
 * 付款单 服务实现类
 * </p>
 *

 * @since 2020-04-23
 */
@RefreshScope
@Service
@Slf4j
public class PaymentAccountServiceImpl extends ServiceImpl<PaymentAccount> implements IPaymentAccountService {

    @Autowired
    private PaymentAccountMapper paymentAccountMapper;
    @Autowired
    private IPaymentAccountMemberService paymentAccountMemberService;
    @Autowired
    private IStatusHistoryService statusHistoryService;
    @Autowired
    private IImpOrderPaymentService impOrderPaymentService;
    @Autowired
    private Snowflake snowflake;
    @Autowired
    private ISerialNumberService serialNumberService;
    @Autowired
    private ISupplierBankService supplierBankService;
    @Autowired
    private ITaskNoticeContentService taskNoticeContentService;
    @Autowired
    private ICustomerService customerService;
    @Autowired
    private ICommonService commonService;
    @Autowired
    private IImpOrderService impOrderService;
    @Autowired
    private IRiskApprovalService riskApprovalService;
    @Autowired
    private IImpQuoteService impQuoteService;
    @Autowired
    private IImpOrderCostService impOrderCostService;
    @Autowired
    private ITransactionFlowService transactionFlowService;
    @Autowired
    private ScmProcessor processor;
    @Autowired
    private ISubjectOverseasService subjectOverseasService;
    @Autowired
    private ISubjectOverseasBankService subjectOverseasBankService;
    @Autowired
    private ICurrencyService currencyService;
    @Resource
    private RedisLockRegistry redisLockRegistry;
    @Value("${redis.lock.time:5}")
    private Integer lockTime;
    @Autowired
    private IImpClauseService impClauseService;
    @Value("${file.directory}")
    private String filePath;
    @Autowired
    private IExportFileService exportFileService;
    @Autowired
    private IShortMessageService shortMessageService;
    @Autowired
    private IPersonService personService;
    @Autowired
    private IWxMessageService wxMessageService;
    @Autowired
    private IWriteOffService writeOffService;

    @Override
    public PageInfo<PaymentAccountVO> pageList(PaymentAccountQTO qto) {
        PageHelper.startPage(qto.getPage(),qto.getLimit());
        Map<String,Object> params = beanMapper.map(qto, Map.class);
        List<PaymentAccountVO> list = paymentAccountMapper.list(params);
        return new PageInfo<>(list);
    }

    @Override
    public PaymentAccountPlusDetailVO detail(Long id, String operation) {
        PaymentAccount paymentAccount = super.getById(id);
        Optional.ofNullable(paymentAccount).orElseThrow(() -> new ServiceException("付款单数据不存在"));
        DomainStatus.getInstance().check(DomainOprationEnum.of(operation), paymentAccount.getStatusId());
        List<PaymentAccountMemberVO> members = paymentAccountMemberService.findByPaymentAccountId(id);
        PaymentAccountVO paymentAccountVO = beanMapper.map(paymentAccount,PaymentAccountVO.class);
        Customer customer = customerService.getById(paymentAccount.getCustomerId());
        if(Objects.nonNull(customer)){
            paymentAccountVO.setCustomerName(customer.getName());
        }
        return new PaymentAccountPlusDetailVO(paymentAccountVO,members);
    }

    @Override
    public EditPaymentPlusVO initEdit(Long id) {
        PaymentAccountPlusDetailVO plusDetail = detail(id,DomainOprationEnum.PAYMENT_ACCOUNT_EDIT.getCode());
        PaymentAccountVO paymentVO = plusDetail.getPaymentAccount();
        EditPaymentVO payment = beanMapper.map(paymentVO,EditPaymentVO.class);
        payment.setPayeeName(paymentVO.getPayee());

        //获取当日中行九点半汇率，此处改成从订单中取，因为申请付汇的时候已经必须汇率时间一致了
        payment.setAccountDate(LocalDateTimeUtils.convertLocalDateRemoveSecond());
        List<PaymentAccountMemberVO> memberVOList = plusDetail.getMembers();
        List<Long> orderIds = memberVOList.stream().map(PaymentAccountMemberVO::getImpOrderId).collect(Collectors.toList());
        List<ImpClauseOrderVO> clauseOrderVOS = impClauseService.checkOrderIdsGoodsRateTime(orderIds,Boolean.TRUE);
        String goodsRateTime = clauseOrderVOS.get(0).getGoodsRateTime();
        payment.setGoodsRateTime(goodsRateTime);
        String rateTime = LocalDateTimeUtils.convertLocalAddHourMinuteStr(payment.getAccountDate(),goodsRateTime);
        RateTypeEnum rateType = RateTypeEnum.PAY_BANK_CHINA_SELL;
        BigDecimal chinaRate = DataCalling.getInstance().obtainBankChinaRate(payment.getCurrencyId(), rateType.getCode(),rateTime);
        TsfPreconditions.checkArgument(Objects.nonNull(chinaRate),new ServiceException(String.format("%s 外汇牌价卖出价暂未公布，请稍后重试",goodsRateTime)));
        payment.setExchangeRate(chinaRate);

        List<ImpOrderPaySituationVO> orderPaySituationVOS = impOrderPaymentService.paySituationByIds(orderIds);
        Map<Long,ImpOrderPaySituationVO> opsMap = orderPaySituationVOS.stream().collect(Collectors.toMap(ImpOrderPaySituationVO::getId,Function.identity()));
        List<EditPaymentMemberVO> list = Lists.newArrayList();
        memberVOList.stream().forEach(member ->{
            ImpOrderPaySituationVO ops = opsMap.get(member.getImpOrderId());

            EditPaymentMemberVO vo = new EditPaymentMemberVO();
            vo.setId(ops.getId());
            vo.setDocNo(ops.getDocNo());
            vo.setOrderDate(ops.getOrderDate());
            vo.setSupplierName(ops.getSupplierName());
            vo.setTotalPrice(ops.getTotalPrice());
            vo.setCurrencyName(ops.getCurrencyName());
            vo.setApplyPayVal(ops.getApplyPayVal().subtract(member.getAccountValue()).setScale(2,BigDecimal.ROUND_HALF_UP));
            vo.setPayVal(ops.getPayVal());
            vo.setThisApplyPayVal(member.getAccountValue());

            list.add(vo);
        });
        return new EditPaymentPlusVO(payment,list);
    }

    @Transactional(rollbackFor = Exception.class)
    @Override
    public PaymentAccount changeStatus(Long id, PaymentAccount paymentAccount, DomainOprationEnum operationEnum, PaymentAccountStatusEnum changeStatusEnum, String memo) {
        if(Objects.isNull(paymentAccount)) {
            paymentAccount = super.getById(id);
        }
        Optional.ofNullable(paymentAccount).orElseThrow(()->new ServiceException("付汇单数据不存在"));
        PaymentAccountStatusEnum originStatus = PaymentAccountStatusEnum.of(paymentAccount.getStatusId());
        paymentAccount.setStatusId(changeStatusEnum.getCode());

        PaymentAccount update = new PaymentAccount();
        update.setStatusId(changeStatusEnum.getCode());
        paymentAccountMapper.updateByExampleSelective(update, Example.builder(PaymentAccount.class).where(TsfWeekendSqls.<PaymentAccount>custom().andEqualTo(false,PaymentAccount::getId,paymentAccount.getId())).build());

        //记录历史状态
        statusHistoryService.saveHistory(operationEnum,
                paymentAccount.getId().toString(),PaymentAccount.class.getName(),
                originStatus.getCode(),originStatus.getName(),
                changeStatusEnum.getCode(),changeStatusEnum.getName(),
                memo
        );
        return paymentAccount;
    }

    @Transactional(rollbackFor = Exception.class)
    @Override
    public void updateForRisk(PaymentAccount updatePaymentAccount) {
        paymentAccountMapper.updateForRisk(updatePaymentAccount);
    }

    @Override
    @Transactional(rollbackFor = Exception.class)
    public void apply(PaymentApplyDTO dto) {
        List<PaymentOrderDTO> paymentOrderDTOS = dto.getOrders();
        List<Long> ids = paymentOrderDTOS.stream().map(PaymentOrderDTO::getOrderId).collect(Collectors.toList());
        ApplyPaymentPlusVO applyPaymentPlusVO = impOrderPaymentService.initApply(ids);
        List<ImpOrderPaySituationVO> orderMembers = applyPaymentPlusVO.getMembers();
        Map<Long,ImpOrderPaySituationVO> orderPayMap = Maps.newLinkedHashMap();
        if(CollUtil.isNotEmpty(orderMembers)){
            orderPayMap = orderMembers.stream().collect(Collectors.toMap(ImpOrderPaySituationVO::getId, Function.identity()));
        }
        ApplyPaymentVO payment = applyPaymentPlusVO.getPayment();

        PaymentAccount paymentAccount = new PaymentAccount();
        paymentAccount.setId(snowflake.nextId());
        paymentAccount.setOldExchangeRate(payment.getExchangeRate());
        paymentAccount.setExchangeRate(payment.getExchangeRate());
        paymentAccount.setAccountValue(BigDecimal.ZERO);
        paymentAccount.setAccountValueCny(BigDecimal.ZERO);

        LocalDateTime now = LocalDateTime.now();
        List<PaymentAccountMember> memberList = Lists.newArrayList();
        List<Long> orderIds = Lists.newArrayList();
        for(PaymentOrderDTO ord : dto.getOrders()){
            if(ord.getApplyPayVal().compareTo(BigDecimal.ZERO) == 1){
                ImpOrderPaySituationVO opay = orderPayMap.get(ord.getOrderId());
                TsfPreconditions.checkArgument(Objects.nonNull(opay),new ServiceException(String.format("订单【%s】可申请付汇金额不足",ord.getOrderNo())));
                PaymentAccountMember member = new PaymentAccountMember();
                member.setId(snowflake.nextId());
                member.setPaymentAccountId(paymentAccount.getId());
                member.setAccountValue(ord.getApplyPayVal());

                BigDecimal allowApplyVal = opay.getTotalPrice().subtract(opay.getApplyPayVal()).setScale(2,BigDecimal.ROUND_HALF_UP);
                if(allowApplyVal.compareTo(member.getAccountValue())==-1){
                    throw new ServiceException(String.format("订单【%s】可申请付汇金额不足",opay.getDocNo()));
                }
                //人民币金额
                member.setAccountValueCny(member.getAccountValue().multiply(paymentAccount.getExchangeRate()).setScale(2,BigDecimal.ROUND_HALF_UP));
                member.setImpOrderId(opay.getId());
                orderIds.add(opay.getId());
                member.setImpOrderNo(opay.getDocNo());
                member.setDateCreated(now);
                memberList.add(member);

                //付汇主单统计
                paymentAccount.setAccountValue(paymentAccount.getAccountValue().add(member.getAccountValue()).setScale(2,BigDecimal.ROUND_HALF_UP));
                paymentAccount.setAccountValueCny(paymentAccount.getAccountValueCny().add(member.getAccountValueCny()).setScale(2,BigDecimal.ROUND_HALF_UP));
            }
        }
        if(paymentAccount.getAccountValue().compareTo(BigDecimal.ZERO) != 1){
            throw new ServiceException("付款申请金额必须大于零");
        }
        //合并付汇验证汇率时间是否一致
        List<ImpClauseOrderVO> impClauseOrderVOS = impClauseService.checkOrderIdsGoodsRateTime(orderIds,Boolean.TRUE);
        paymentAccount.setDocNo(serialNumberService.generateDocNo(SerialNumberTypeEnum.IMP_PAYMENT));
        paymentAccount.setAccountDate(payment.getAccountDate());
        paymentAccount.setCustomerId(payment.getCustomerId());
        SupplierBankVO supplierBank = supplierBankService.accurate(paymentAccount.getCustomerId(),dto.getPayment().getPayeeName(),dto.getPayment().getPayeeBankName());
        TsfPreconditions.checkArgument(Objects.nonNull(supplierBank),new ServiceException(String.format("收款人【%s】与收款银行【%s】信息不匹配请检查",dto.getPayment().getPayeeName(),dto.getPayment().getPayeeBankName())));
        TsfPreconditions.checkArgument(Objects.equals(supplierBank.getDisabled(),Boolean.FALSE),new ServiceException(String.format("收款银行【%s】已被禁用",dto.getPayment().getPayeeBankName())));

        paymentAccount.setPayeeId(supplierBank.getSupplierId());
        paymentAccount.setPayee(supplierBank.getSupplierName());
        paymentAccount.setPayeeBankId(supplierBank.getId());
        paymentAccount.setPayeeBankName(supplierBank.getName());
        paymentAccount.setPayeeBankAccountNo(supplierBank.getAccount());
        paymentAccount.setSwiftCode(supplierBank.getSwiftCode());
        paymentAccount.setIbankCode(supplierBank.getCode());
        paymentAccount.setPayeeBankAddress(supplierBank.getAddress());
        paymentAccount.setPaymentTerm(dto.getPayment().getPaymentTerm());
        paymentAccount.setCurrencyId(payment.getCurrencyId());
        paymentAccount.setCurrencyName(payment.getCurrencyName());
        paymentAccount.setFictitious(Boolean.FALSE);
        //手续费承担方式
        paymentAccount.setCounterFeeType(dto.getPayment().getCounterFeeType());
        //计算付汇手续费
        paymentAccount.setBankFee(calculationFee(orderIds.get(0),paymentAccount.getPaymentTerm(),paymentAccount.getCounterFeeType(),paymentAccount.getSwiftCode()));
        paymentAccount.setMemo(dto.getPayment().getMemo());
        PaymentAccountStatusEnum statusEnum = PaymentAccountStatusEnum.WAIT_SW_EXAMINE;
        paymentAccount.setStatusId(statusEnum.getCode());

        //获取公司默认境外公司
        SubjectOverseasVO overseas = subjectOverseasService.defOverseas();
        if(Objects.nonNull(overseas)){
            paymentAccount.setPayer(overseas.getName());
        }
        try {
            super.saveNonNull(paymentAccount);
        } catch (DuplicateKeyException e) {
            throw new ServiceException("系统单号已经被占用，请稍后再试");
        }
        paymentAccountMemberService.savaBatch(memberList);
        //写入订单付汇单号
        orderIds.stream().forEach(orderId ->{
            List<String> payNos =  paymentAccountMemberService.findByOrderIdByPayNos(orderId);
            impOrderService.updateOrderPaymentNo(orderId,CollUtil.isNotEmpty(payNos)?String.join(",",payNos):"");
        });

        //记录历史状态
        statusHistoryService.saveHistory(DomainOprationEnum.PAYMENT_ACCOUNT_APPLY,
                paymentAccount.getId().toString(), PaymentAccount.class.getName(),
                statusEnum.getCode(),statusEnum.getName(),"付款申请");

        //发送任务通知
        TaskNoticeContentDTO taskNoticeContentDTO = new TaskNoticeContentDTO();
        taskNoticeContentDTO.setDocumentType(DomainTypeEnum.PAYMENTACCOUNT.getCode());
        taskNoticeContentDTO.setDocumentId(paymentAccount.getId().toString());
        taskNoticeContentDTO.setCustomerId(paymentAccount.getCustomerId());
        taskNoticeContentDTO.setOperationCode(DomainOprationEnum.PAYMENT_ACCOUNT_AUDIT.getCode());
        taskNoticeContentDTO.setContent(String.format("【%s】提交了一单付款申请【%s】需要您审核。", SecurityUtil.getCurrentPersonName(),paymentAccount.getDocNo()));
        taskNoticeContentDTO.setQueryParamsMap(ImmutableMap.of("docNo",paymentAccount.getDocNo()));
        taskNoticeContentService.add(taskNoticeContentDTO);
    }

    @Override
    @Transactional(rollbackFor = Exception.class)
    public void edit(PaymentApplyDTO dto) {
        PaymentAccountDTO paymentAccountDTO = dto.getPayment();
        String lockKey = DistributedLockEnum.PAYMENT_EDIT.getCode() + ":" + paymentAccountDTO.getId();
        Lock lock = redisLockRegistry.obtain(lockKey);
        boolean isLock;
        try {
            isLock = lock.tryLock(lockTime, TimeUnit.SECONDS);
            if(!isLock) {
                log.error(String.format("未获取到锁，付款单id：【%s】",paymentAccountDTO.getId()));
                throw new ServiceException("服务拥挤，请稍后再试");
            }
            PaymentAccount paymentAccount = super.getById(paymentAccountDTO.getId());
            TsfPreconditions.checkArgument(Objects.nonNull(paymentAccount),new ServiceException("付汇单不存在"));
            DomainOprationEnum oprationEnum = DomainOprationEnum.PAYMENT_ACCOUNT_EDIT;
            PaymentAccountStatusEnum historyStatus = PaymentAccountStatusEnum.of(paymentAccount.getStatusId());
            DomainStatus.getInstance().check(oprationEnum, paymentAccount.getStatusId());


            List<PaymentAccountMemberVO> paymentAccountMemberVOS = paymentAccountMemberService.findByPaymentAccountId(paymentAccount.getId());
            List<Long> orderIds = paymentAccountMemberVOS.stream().map(PaymentAccountMemberVO::getImpOrderId).collect(Collectors.toList());
            List<Long> memberIds = paymentAccountMemberVOS.stream().map(PaymentAccountMemberVO::getId).collect(Collectors.toList());
            //删除原明细数据
            paymentAccountMemberService.removeByIds(memberIds);

            //重新生成明细数据
            ApplyPaymentPlusVO applyPaymentPlusVO = impOrderPaymentService.initApply(orderIds);
            List<ImpOrderPaySituationVO> orderMembers = applyPaymentPlusVO.getMembers();
            Map<Long,ImpOrderPaySituationVO> orderPayMap = Maps.newLinkedHashMap();
            if(CollUtil.isNotEmpty(orderMembers)){
                orderPayMap = orderMembers.stream().collect(Collectors.toMap(ImpOrderPaySituationVO::getId, Function.identity()));
            }
            ApplyPaymentVO payment = applyPaymentPlusVO.getPayment();
            paymentAccount.setOldExchangeRate(payment.getExchangeRate());
            paymentAccount.setExchangeRate(payment.getExchangeRate());
            paymentAccount.setAccountValue(BigDecimal.ZERO);
            paymentAccount.setAccountValueCny(BigDecimal.ZERO);

            LocalDateTime now = LocalDateTime.now();
            List<PaymentAccountMember> memberList = Lists.newArrayList();
            for(PaymentOrderDTO ord : dto.getOrders()) {
                if (ord.getApplyPayVal().compareTo(BigDecimal.ZERO) == 1) {
                    ImpOrderPaySituationVO opay = orderPayMap.get(ord.getOrderId());
                    TsfPreconditions.checkArgument(Objects.nonNull(opay),new ServiceException(String.format("订单【%s】可申请付汇金额不足",ord.getOrderNo())));
                    PaymentAccountMember member = new PaymentAccountMember();
                    member.setId(snowflake.nextId());
                    member.setPaymentAccountId(paymentAccount.getId());
                    member.setAccountValue(ord.getApplyPayVal());

                    BigDecimal allowApplyVal = opay.getTotalPrice().subtract(opay.getApplyPayVal()).setScale(2,BigDecimal.ROUND_HALF_UP);
                    if(allowApplyVal.compareTo(member.getAccountValue())==-1){
                        throw new ServiceException(String.format("订单【%s】可申请付汇金额不足",opay.getDocNo()));
                    }
                    //人民币金额
                    member.setAccountValueCny(member.getAccountValue().multiply(paymentAccount.getExchangeRate()).setScale(2,BigDecimal.ROUND_HALF_UP));
                    member.setImpOrderId(opay.getId());

                    member.setImpOrderNo(opay.getDocNo());
                    member.setDateCreated(now);
                    memberList.add(member);

                    //付汇主单统计
                    paymentAccount.setAccountValue(paymentAccount.getAccountValue().add(member.getAccountValue()).setScale(2,BigDecimal.ROUND_HALF_UP));
                    paymentAccount.setAccountValueCny(paymentAccount.getAccountValueCny().add(member.getAccountValueCny()).setScale(2,BigDecimal.ROUND_HALF_UP));

                    //防止新加订单号
                    if(!orderIds.contains(opay.getId())){
                        orderIds.add(opay.getId());
                    }
                }
            }
            if(paymentAccount.getAccountValue().compareTo(BigDecimal.ZERO) != 1){
                throw new ServiceException("付款申请金额必须大于零");
            }
            paymentAccount.setAccountDate(payment.getAccountDate());
            paymentAccount.setCustomerId(payment.getCustomerId());

            SupplierBankVO supplierBank = supplierBankService.accurate(paymentAccount.getCustomerId(),dto.getPayment().getPayeeName(),dto.getPayment().getPayeeBankName());
            TsfPreconditions.checkArgument(Objects.nonNull(supplierBank),new ServiceException(String.format("收款人【%s】与收款银行【%s】信息不匹配请检查",dto.getPayment().getPayeeName(),dto.getPayment().getPayeeBankName())));
            TsfPreconditions.checkArgument(Objects.equals(supplierBank.getDisabled(),Boolean.FALSE),new ServiceException(String.format("收款银行【%s】已被禁用",dto.getPayment().getPayeeBankName())));
            paymentAccount.setPayeeId(supplierBank.getSupplierId());
            paymentAccount.setPayee(supplierBank.getSupplierName());
            paymentAccount.setPayeeBankId(supplierBank.getId());
            paymentAccount.setPayeeBankName(supplierBank.getName());
            paymentAccount.setPayeeBankAccountNo(supplierBank.getAccount());
            paymentAccount.setSwiftCode(supplierBank.getSwiftCode());
            paymentAccount.setIbankCode(supplierBank.getCode());
            paymentAccount.setPayeeBankAddress(supplierBank.getAddress());
            paymentAccount.setPaymentTerm(dto.getPayment().getPaymentTerm());
            paymentAccount.setCurrencyId(payment.getCurrencyId());
            paymentAccount.setCurrencyName(payment.getCurrencyName());
            paymentAccount.setMemo(dto.getPayment().getMemo());

            //手续费承担方式
            paymentAccount.setCounterFeeType(dto.getPayment().getCounterFeeType());
            //计算付汇手续费
            paymentAccount.setBankFee(calculationFee(orderIds.get(0),paymentAccount.getPaymentTerm(),paymentAccount.getCounterFeeType(),paymentAccount.getSwiftCode()));

            PaymentAccountStatusEnum statusEnum = PaymentAccountStatusEnum.WAIT_SW_EXAMINE;
            paymentAccount.setStatusId(statusEnum.getCode());

            super.updateById(paymentAccount);
            paymentAccountMemberService.savaBatch(memberList);

            //写入订单付汇单号
            orderIds.stream().forEach(orderId ->{
                List<String> payNos =  paymentAccountMemberService.findByOrderIdByPayNos(orderId);
                impOrderService.updateOrderPaymentNo(orderId,CollUtil.isNotEmpty(payNos)?String.join(",",payNos):"");
            });

            //记录历史状态
            statusHistoryService.saveHistory(oprationEnum,
                    paymentAccount.getId().toString(), PaymentAccount.class.getName(),
                    historyStatus.getCode(),historyStatus.getName(),
                    statusEnum.getCode(),statusEnum.getName(),"付汇单修改");

            //清空付款单任务通知
            clearTaskNotice(paymentAccount.getId());

            //发送任务通知
            TaskNoticeContentDTO taskNoticeContentDTO = new TaskNoticeContentDTO();
            taskNoticeContentDTO.setDocumentType(DomainTypeEnum.PAYMENTACCOUNT.getCode());
            taskNoticeContentDTO.setDocumentId(paymentAccount.getId().toString());
            taskNoticeContentDTO.setCustomerId(paymentAccount.getCustomerId());
            taskNoticeContentDTO.setOperationCode(DomainOprationEnum.PAYMENT_ACCOUNT_AUDIT.getCode());
            taskNoticeContentDTO.setContent(String.format("付款申请【%s】已完成修改，请您尽快审核。",paymentAccount.getDocNo()));
            taskNoticeContentDTO.setQueryParamsMap(ImmutableMap.of("docNo",paymentAccount.getDocNo()));
            taskNoticeContentService.add(taskNoticeContentDTO);
        } catch (InterruptedException e) {
            log.error("付款单修改获取锁异常",e);
            throw new ServiceException("服务拥挤，请稍后再试");
        } finally {
            //释放锁
            lock.unlock();
        }
    }

    @Override
    @Transactional(rollbackFor = Exception.class)
    public void clientEdit(PaymentApplyDTO dto) {
        edit(dto);
    }

    private static final Map<DomainOprationEnum,List<PaymentAccountStatusEnum>> ALLOW_STATUS_MAP = new LinkedHashMap<DomainOprationEnum,List<PaymentAccountStatusEnum>>(){{
        //商务审核
        put(DomainOprationEnum.PAYMENT_ACCOUNT_AUDIT,Arrays.asList(PaymentAccountStatusEnum.WAITT_EDIT,PaymentAccountStatusEnum.WAIT_FK_EXAMINE,PaymentAccountStatusEnum.CONFIRM_BANK));
        //作废付款
        put(DomainOprationEnum.PAYMENT_ACCOUNT_INVALID,Arrays.asList(PaymentAccountStatusEnum.TO_VOID));
    }};
    //验证操作
    private static void verifyOperation(TaskDTO dto){
        List<PaymentAccountStatusEnum> cbseList = ALLOW_STATUS_MAP.get(DomainOprationEnum.of(dto.getOperation()));
        TsfPreconditions.checkArgument(CollUtil.isNotEmpty(cbseList),new ServiceException("不支持的操作,请刷新页面"));
        TsfPreconditions.checkArgument(cbseList.contains(PaymentAccountStatusEnum.of(dto.getNewStatusCode())),new ServiceException("当前状态不支持此操作,请刷新页面"));
    }

    @Override
    @Transactional(rollbackFor = Exception.class)
    public void processJump(TaskDTO dto){
        verifyOperation(dto);
        PaymentAccountStatusEnum status = PaymentAccountStatusEnum.of(dto.getNewStatusCode());
        // 作废付款单
        if(Objects.equals(PaymentAccountStatusEnum.TO_VOID,status)){
            toVoid(dto);
        }else{
            PaymentAccount paymentAccount = commonService.changeDocumentStatus(dto);
            DomainOprationEnum oprationEnum = DomainOprationEnum.of(dto.getOperation());
            PaymentAccountStatusEnum statusEnum = PaymentAccountStatusEnum.of(paymentAccount.getStatusId());
            switch (oprationEnum){
                case PAYMENT_ACCOUNT_AUDIT://商务审核
                    if(Objects.equals(PaymentAccountStatusEnum.WAITT_EDIT,statusEnum)){//退回修改
                        //发送任务通知
                        TaskNoticeContentDTO taskNoticeContentDTO = new TaskNoticeContentDTO();
                        taskNoticeContentDTO.setDocumentType(DomainTypeEnum.PAYMENTACCOUNT.getCode());
                        taskNoticeContentDTO.setDocumentId(paymentAccount.getId().toString());
                        taskNoticeContentDTO.setCustomerId(paymentAccount.getCustomerId());
                        taskNoticeContentDTO.setOperationCode(DomainOprationEnum.PAYMENT_ACCOUNT_EDIT.getCode());
                        taskNoticeContentDTO.setContent(String.format("付款申请【%s】被退回需要您修改：%s", paymentAccount.getDocNo(), StringUtils.removeSpecialSymbol(dto.getMemo())));
                        taskNoticeContentDTO.setQueryParamsMap(ImmutableMap.of("docNo",paymentAccount.getDocNo()));
                        taskNoticeContentService.add(taskNoticeContentDTO);

                        //发送短信通知客户
                        LinkedHashMap<String,String> paramsMap = new LinkedHashMap<>();
                        paramsMap.put("docNo",paymentAccount.getDocNo());
                        SmsNoticeMessageDTO smsNoticeMessageDTO = new SmsNoticeMessageDTO();
                        smsNoticeMessageDTO.setMessageNodeEnum(MessageNodeEnum.PAY_BACK);
                        Person person = personService.getPhoneByCustomerId(paymentAccount.getCustomerId());
                        smsNoticeMessageDTO.setPhoneNo(Objects.nonNull(person) ? person.getPhone() : "");
                        smsNoticeMessageDTO.setParams(paramsMap);
                        shortMessageService.sendNoticeMessage(smsNoticeMessageDTO);

                        //发送微信通知
                        WxNoticeMessageDTO wxNoticeMessageDTO = new WxNoticeMessageDTO();
                        wxNoticeMessageDTO.setWxgzhOpenid(Objects.nonNull(person) ? person.getWxgzhOpenid() : "");
                        wxNoticeMessageDTO.setWxMessageDefineTemplate(WxMessageDefineTemplate.PAY_BACK);
                        wxNoticeMessageDTO.setIsNavToMiniprogram(Boolean.TRUE);
                        wxNoticeMessageDTO.setMiniprogram(new WxMiniProgram("pages/finance/payment/manage?id=" + paymentAccount.getId()));
                        LinkedHashMap<String, WxNoticeAttr> params = new LinkedHashMap<>();
                        params.put("first",new WxNoticeAttr("您的付汇单已退回待修改"));
                        params.put("keyword1",new WxNoticeAttr(paymentAccount.getDocNo()));
                        params.put("keyword2",new WxNoticeAttr(dto.getMemo()));
                        params.put("remark",new WxNoticeAttr("点击修改","#9C9C9C"));
                        wxNoticeMessageDTO.setParams(params);
                        wxMessageService.sendWxNotice(wxNoticeMessageDTO);
                    }else if(Objects.equals(PaymentAccountStatusEnum.WAIT_FK_EXAMINE,statusEnum)){//提交风控审批
                        RiskApproval ra = new RiskApproval();
                        ra.setDocType(RiskApprovalDocTypeEnum.PAYMENT_ACCOUNT_INSUFFICIENT_MOMEY.getCode());
                        ra.setDocId(paymentAccount.getId().toString());
                        ra.setDocNo(paymentAccount.getDocNo());
                        ra.setCustomerId(paymentAccount.getCustomerId());
                        ra.setSubmitter(SecurityUtil.getCurrentPersonName());
                        ra.setApprovalReason(dto.getMemo());
                        riskApprovalService.submitRiskApproval(ra);
                    }else if(Objects.equals(PaymentAccountStatusEnum.CONFIRM_BANK,statusEnum)){//审核通过
                        //验证是否需要风控审批
                        PaymentCostInfoVO paymentCostInfoVO = impOrderCostService.paymentCostInfo(paymentAccount.getId());
                        if(Objects.equals(paymentCostInfoVO.getIsAdopt(),Boolean.FALSE)&&!Objects.equals(paymentCostInfoVO.getFkAdopt(),Boolean.TRUE)){
                            throw new ServiceException(StringUtils.null2EmptyWithTrim(paymentCostInfoVO.getReason()).concat("需要风控审批"));
                        }
                        //付汇单费用挂载订单 写入交易流水
                        mountOrderCost(paymentAccount);

                        //发送任务通知
                        TaskNoticeContentDTO taskNoticeContentDTO = new TaskNoticeContentDTO();
                        taskNoticeContentDTO.setDocumentType(DomainTypeEnum.PAYMENTACCOUNT.getCode());
                        taskNoticeContentDTO.setDocumentId(paymentAccount.getId().toString());
                        taskNoticeContentDTO.setCustomerId(paymentAccount.getCustomerId());
                        taskNoticeContentDTO.setOperationCode(DomainOprationEnum.PAYMENT_WAIT_CONFIRM_BANK.getCode());
                        taskNoticeContentDTO.setContent(String.format("付款申请【%s】已审核通过请尽快确定付款行", paymentAccount.getDocNo()));
                        taskNoticeContentDTO.setQueryParamsMap(ImmutableMap.of("docNo",paymentAccount.getDocNo()));
                        taskNoticeContentService.add(taskNoticeContentDTO);

                        //通知核销
                        CostWriteOffDTO cwdto = new CostWriteOffDTO();
                        cwdto.setTenant("");
                        cwdto.setCustomerId(paymentAccount.getCustomerId());
                        processor.sendCostWriteOff(cwdto);
                    }
                    break;
                default:
                   throw new ServiceException("状态错误");
            }
        }

    }
    //订单还未审核状态
    private final static List<String> orderNotPassStatusList = Arrays.asList(
            ImpOrderStatusEnum.TEMP_SAVED.getCode(),
            ImpOrderStatusEnum.WAIT_EXAMINE.getCode(),
            ImpOrderStatusEnum.WAIT_PRICE.getCode()
    );
    //付汇单费用挂载订单
    public void mountOrderCost(PaymentAccount paymentAccount){
        //获取付汇明细
        List<PaymentAccountMemberVO> memberVOS = paymentAccountMemberService.findByPaymentAccountId(paymentAccount.getId());
        List<ImpOrderCost> costList = Lists.newArrayList();
        LocalDateTime happenDate = LocalDateTimeUtils.convertLocalDate();//发生日期
        Map<Long, ImpQuote> quoteMap = Maps.newLinkedHashMap();
        for(int i=0;i<memberVOS.size();i++){
            PaymentAccountMemberVO mvo = memberVOS.get(i);
            TsfPreconditions.checkArgument(!orderNotPassStatusList.contains(mvo.getStatusId()),new ServiceException(String.format("订单【%s】还未审核通过，无法付汇",mvo.getImpOrderNo())));
            ImpOrderCost cost = new ImpOrderCost();
            cost.setId(snowflake.nextId());
            cost.setImpOrderId(mvo.getImpOrderId());
            cost.setExpenseSubjectId("A0005");//货款
            cost.setCostClassify(CostClassifyEnum.HK.getCode());//分类
            cost.setActualAmount(mvo.getAccountValueCny());//原始金额
            cost.setReceAmount(mvo.getAccountValueCny());//应收金额
            cost.setIsAutomatic(Boolean.FALSE);//系统生成(防止被删除)
            cost.setOperator(SecurityUtil.getCurrentPersonName());
            cost.setIsAllowEdit(Boolean.FALSE);//不允许修改
            cost.setAcceAmount(BigDecimal.ZERO);//已收金额
            cost.setIsFirstWriteOff(Boolean.FALSE);//是否需要在账期内冲销(优先核销)
            cost.setIsLock(Boolean.TRUE);//锁定允许核销
            cost.setHappenDate(happenDate);//发生日期
            //计算账期
            ImpQuote impQuote = quoteMap.get(mvo.getImpQuoteId());
            if(Objects.isNull(impQuote)){
                impQuote = impQuoteService.getById(mvo.getImpQuoteId());
                quoteMap.put(mvo.getImpQuoteId(),impQuote);
            }
            //到期日
            LocalDateTime periodDate = AccountUtils.goodFeesPeriod(cost.getHappenDate(),impQuote);
            cost.setPeriodDate(periodDate);
            cost.setLateFeeDate((impQuote.getGraceDay()>0)?cost.getPeriodDate().plusDays(impQuote.getGraceDay()):cost.getPeriodDate());
            cost.setOverdueRate(impQuote.getOverdueRate());
            cost.setMark(paymentAccount.getDocNo());
            cost.setMemo(String.format("付款申请单号：%s",paymentAccount.getDocNo()));
            costList.add(cost);

            //付汇手续费(存在则挂入第一个订单)
            if(i==0&&paymentAccount.getBankFee().compareTo(BigDecimal.ZERO)==1){
                cost = new ImpOrderCost();
                cost.setId(snowflake.nextId());
                cost.setImpOrderId(mvo.getImpOrderId());
                cost.setExpenseSubjectId("A0006");//货款
                cost.setCostClassify(CostClassifyEnum.HK.getCode());//分类
                cost.setActualAmount(paymentAccount.getBankFee());//原始金额
                cost.setReceAmount(paymentAccount.getBankFee());//应收金额
                cost.setIsAutomatic(Boolean.FALSE);//系统生成(防止被删除)
                cost.setOperator(SecurityUtil.getCurrentPersonName());
                cost.setIsAllowEdit(Boolean.TRUE);//允许修改
                cost.setAcceAmount(BigDecimal.ZERO);//已收金额
                cost.setIsFirstWriteOff(Boolean.FALSE);//是否需要在账期内冲销(优先核销)
                cost.setIsLock(Boolean.TRUE);//锁定允许核销
                cost.setHappenDate(happenDate);//发生日期
                cost.setPeriodDate(periodDate);//账期日期
                cost.setLateFeeDate((impQuote.getGraceDay()>0)?cost.getPeriodDate().plusDays(impQuote.getGraceDay()):cost.getPeriodDate());
                cost.setOverdueRate(impQuote.getOverdueRate());
                cost.setMark(paymentAccount.getDocNo());
                cost.setMemo(String.format("付款申请单号：%s",paymentAccount.getDocNo()));
                costList.add(cost);
            }
        }
        //保存订单费用
        impOrderCostService.savaBatch(costList);
        //写入交易流水
        TransactionFlowDTO param = new TransactionFlowDTO();
        param.setCustomerId(paymentAccount.getCustomerId());
        param.setTransactionType(TransactionTypeEnum.IMP_payment.getCode());
        param.setTransactionCategory(TransactionCategoryEnum.OUTCOME.getCode());
        param.setTransactionNo(paymentAccount.getDocNo());
        param.setAmount(paymentAccount.getAccountValueCny().add(paymentAccount.getBankFee()).setScale(2,BigDecimal.ROUND_HALF_UP));
        param.setMemo(String.format("原币金额:%s(%s);汇率:%s;人民币金额:%s;手续费金额：%s",paymentAccount.getAccountValue().toString(),paymentAccount.getCurrencyName(),paymentAccount.getExchangeRate().toString(),paymentAccount.getAccountValueCny().toString(),paymentAccount.getBankFee().toString()));
        transactionFlowService.recordTransactionFlow(param);
    }

    //作废付款单(不能单独调用，先调用流程跳转)
    @Transactional(rollbackFor = Exception.class)
    public void toVoid(TaskDTO dto){
        PaymentAccount paymentAccount = super.getById(dto.getDocumentId());
        PaymentAccountStatusEnum toVoidStatus = PaymentAccountStatusEnum.TO_VOID;
        PaymentAccountStatusEnum historyStatus = PaymentAccountStatusEnum.of(paymentAccount.getStatusId());
        if(Objects.equals(toVoidStatus,historyStatus)){
            throw new ServiceException("当前单据已作废");
        }
        //需要反核销和调整流水
        // 出纳确定付款行,待付汇，已完成
        List<String> status = Arrays.asList(PaymentAccountStatusEnum.CONFIRM_BANK.getCode(),PaymentAccountStatusEnum.WAIT_PAY.getCode(),PaymentAccountStatusEnum.COMPLETED.getCode());
        if(status.contains(historyStatus.getCode())){
            BigDecimal totalOrderCost = BigDecimal.ZERO;//还原订单费用总金额
            // 根据付汇单查询订单货款费用
            List<ImpOrderCost> impOrderCosts = impOrderCostService.getByMarkAndExpenseSubject(paymentAccount.getDocNo(),"A0005");
            for(ImpOrderCost impOrderCost : impOrderCosts){
                totalOrderCost = totalOrderCost.add(impOrderCost.getReceAmount());
                //取消费用核销
                writeOffService.cancelWriteOffByImpOrderCost(impOrderCost);
                //删除此订单费用
                impOrderCostService.removeById(impOrderCost.getId());
            }
            if(totalOrderCost.compareTo(paymentAccount.getAccountValueCny()) != 0){
                throw new ServiceException("取消订单费用金额与付汇单人民币金额不一致");
            }
            // 还原流水
            TransactionFlowDTO param = new TransactionFlowDTO();
            param.setCustomerId(paymentAccount.getCustomerId());
            param.setTransactionType(TransactionTypeEnum.IMP_payment.getCode());
            param.setTransactionCategory(TransactionCategoryEnum.INCOME.getCode());
            param.setTransactionNo(paymentAccount.getDocNo());
            param.setAmount(paymentAccount.getAccountValueCny());
            param.setMemo(String.format("作废付汇，原币金额:%s(%s);汇率:%s;人民币金额:%s;",paymentAccount.getAccountValue().toString(),paymentAccount.getCurrencyName(),paymentAccount.getExchangeRate().toString(),paymentAccount.getAccountValueCny().toString()));
            transactionFlowService.recordTransactionFlow(param);
        }
        //更新状态
        paymentAccount.setStatusId(toVoidStatus.getCode());
        super.updateById(paymentAccount);

        //记录历史状态
        statusHistoryService.saveHistory(DomainOprationEnum.PAYMENT_ACCOUNT_INVALID,
                paymentAccount.getId().toString(), PaymentAccount.class.getName(),
                historyStatus.getCode(),historyStatus.getName(),
                toVoidStatus.getCode(),toVoidStatus.getName(),StringUtils.removeSpecialSymbol(dto.getMemo()));

        // 状态处于风控审核 删除风控审核单据
        if(Objects.equals(PaymentAccountStatusEnum.WAIT_FK_EXAMINE,historyStatus)){
            riskApprovalService.removeByDoc(RiskApprovalDocTypeEnum.PAYMENT_ACCOUNT_RATE_ADJUST.getCode(),paymentAccount.getId().toString());
            riskApprovalService.removeByDoc(RiskApprovalDocTypeEnum.PAYMENT_ACCOUNT_INSUFFICIENT_MOMEY.getCode(),paymentAccount.getId().toString());
        }

        //删除付款单明细数据
        List<PaymentAccountMemberVO> members = paymentAccountMemberService.findByPaymentAccountId(paymentAccount.getId());
        List<Long> memberIds = Lists.newArrayList();
        List<Long> orderIds = Lists.newArrayList();
        members.stream().forEach(vo ->{
            memberIds.add(vo.getId());
            orderIds.add(vo.getImpOrderId());
        });
        if(CollUtil.isNotEmpty(memberIds)){
            //删除明细
            paymentAccountMemberService.removeByIds(memberIds);
        }
        //更新订单绑定的付汇单号
        orderIds.stream().forEach(orderId ->{
            List<String> payNos =  paymentAccountMemberService.findByOrderIdByPayNos(orderId);
            impOrderService.updateOrderPaymentNo(orderId,CollUtil.isNotEmpty(payNos)?String.join(",",payNos):"");
        });
        //清空付款单任务通知
        clearTaskNotice(paymentAccount.getId());
        //发送短信通知客户
        LinkedHashMap<String,String> paramsMap = new LinkedHashMap<>();
        paramsMap.put("docNo",paymentAccount.getDocNo());
        SmsNoticeMessageDTO smsNoticeMessageDTO = new SmsNoticeMessageDTO();
        smsNoticeMessageDTO.setMessageNodeEnum(MessageNodeEnum.PAY_INVALID);
        Person person = personService.getPhoneByCustomerId(paymentAccount.getCustomerId());
        smsNoticeMessageDTO.setPhoneNo(Objects.nonNull(person) ? person.getPhone() : "");
        smsNoticeMessageDTO.setParams(paramsMap);
        shortMessageService.sendNoticeMessage(smsNoticeMessageDTO);

        Customer customer = customerService.getById(paymentAccount.getCustomerId());
        if(Objects.nonNull(person) && Objects.nonNull(customer)) {
            //发送微信通知
            WxNoticeMessageDTO tempWxNoticeMessageDTO = new WxNoticeMessageDTO();
            tempWxNoticeMessageDTO.setWxgzhOpenid(Objects.nonNull(person) ? person.getWxgzhOpenid() : "");
            tempWxNoticeMessageDTO.setWxMessageDefineTemplate(WxMessageDefineTemplate.PAY_INVALID);
            tempWxNoticeMessageDTO.setIsNavToMiniprogram(Boolean.TRUE);
            tempWxNoticeMessageDTO.setMiniprogram(new WxMiniProgram("pages/finance/payment/info?id=" + paymentAccount.getId()));
            LinkedHashMap<String, WxNoticeAttr> tempParams = new LinkedHashMap<>();
            tempParams.put("first",new WxNoticeAttr("付汇作废通知"));
            String invalidContent = StrUtil.format( "尊敬的{}，您有一笔付汇单号为{}，付汇金额为{}的付汇单作废",customer.getName(),paymentAccount.getDocNo(),StringUtils.formatCurrency(paymentAccount.getAccountValue()));
            tempParams.put("keyword1",new WxNoticeAttr(invalidContent,"#9C9C9C"));
            tempParams.put("keyword2",new WxNoticeAttr(dto.getMemo()));
            tempParams.put("remark",new WxNoticeAttr("点击查看该作废付汇单信息","#9C9C9C"));
            tempWxNoticeMessageDTO.setParams(tempParams);
            wxMessageService.sendWxNotice(tempWxNoticeMessageDTO);
        }
    }
    //清空付款单任务通知
    public void clearTaskNotice(Long id){
        taskNoticeContentService.deleteTaskNotice(DomainTypeEnum.PAYMENTACCOUNT.getCode(), id, "");
    }

    @Override
    @Transactional(rollbackFor = Exception.class)
    public void adjustRate(AdjustRateDTO dto) {
        PaymentAccount paymentAccount = super.getById(dto.getId());
        TsfPreconditions.checkArgument(Objects.nonNull(paymentAccount),new ServiceException("付汇单不存在"));
        DomainOprationEnum oprationEnum = DomainOprationEnum.PAYMENT_RATE_ADJUST;
        PaymentAccountStatusEnum historyStatus = PaymentAccountStatusEnum.of(paymentAccount.getStatusId());
        DomainStatus.getInstance().check(oprationEnum, paymentAccount.getStatusId());

        PaymentAccountStatusEnum statusEnum = PaymentAccountStatusEnum.WAIT_FK_EXAMINE;//待风控审核
        paymentAccount.setStatusId(statusEnum.getCode());
        //调整汇率重新计算
        paymentAccount.setOldExchangeRate(paymentAccount.getExchangeRate());
        adjustRateRecalculate(paymentAccount,dto.getAdjustRate());

        //转入风控审批
        RiskApproval ra = new RiskApproval();
        ra.setDocType(RiskApprovalDocTypeEnum.PAYMENT_ACCOUNT_RATE_ADJUST.getCode());
        ra.setDocId(paymentAccount.getId().toString());
        ra.setDocNo(paymentAccount.getDocNo());
        ra.setCustomerId(paymentAccount.getCustomerId());
        ra.setSubmitter(SecurityUtil.getCurrentPersonName());
        ra.setApprovalReason("汇率由["+paymentAccount.getOldExchangeRate()+"]调整["+paymentAccount.getExchangeRate()+"] "+StringUtils.removeSpecialSymbol(dto.getAdjustInfo()));
        riskApprovalService.submitRiskApproval(ra);

        //记录历史状态
        statusHistoryService.saveHistory(oprationEnum,
                paymentAccount.getId().toString(), PaymentAccount.class.getName(),
                historyStatus.getCode(),historyStatus.getName(),
                statusEnum.getCode(),statusEnum.getName(),StringUtils.removeSpecialSymbol(dto.getAdjustInfo()));

        //清空付款单任务通知
        clearTaskNotice(paymentAccount.getId());
    }

    @Override
    @Transactional(rollbackFor = Exception.class)
    public void adjustBankFee(AdjustBankFeeDTO dto) {
        PaymentAccount paymentAccount = super.getById(dto.getId());
        TsfPreconditions.checkArgument(Objects.nonNull(paymentAccount),new ServiceException("付汇单不存在"));
        DomainOprationEnum oprationEnum = DomainOprationEnum.PAYMENT_RATE_BANK_FEE;
        DomainStatus.getInstance().check(oprationEnum, paymentAccount.getStatusId());
        paymentAccount.setBankFee(dto.getAdjustBankFee());
        super.updateById(paymentAccount);
    }

    //调整汇率重新计算
    @Transactional(rollbackFor = Exception.class)
    public void adjustRateRecalculate(PaymentAccount paymentAccount,BigDecimal rate){
        if(rate.compareTo(BigDecimal.ZERO)!=1){
            throw new ServiceException("调整后汇率必须大于零");
        }
        if(paymentAccount.getExchangeRate().compareTo(rate)==0){
            throw new ServiceException("汇率未做调整");
        }
        paymentAccount.setExchangeRate(rate);
        paymentAccount.setAccountValueCny(BigDecimal.ZERO);
        List<PaymentAccountMember> memberList = paymentAccountMemberService.getByPaymentAccountId(paymentAccount.getId());
        memberList.stream().forEach(member ->{
            //人民币金额
            member.setAccountValueCny(member.getAccountValue().multiply(paymentAccount.getExchangeRate()).setScale(2,BigDecimal.ROUND_HALF_UP));
            //付汇主单统计
            paymentAccount.setAccountValueCny(paymentAccount.getAccountValueCny().add(member.getAccountValueCny()).setScale(2,BigDecimal.ROUND_HALF_UP));

            paymentAccountMemberService.updateById(member);
        });
        super.updateById(paymentAccount);
    }

    @Override
    @Transactional(rollbackFor = Exception.class)
    public void completeAdjustRateRisk(Long orderId, Boolean isPass, String memo) {
        PaymentAccount paymentAccount = super.getById(orderId);
        TsfPreconditions.checkArgument(Objects.nonNull(paymentAccount),new ServiceException("付汇单不存在"));
        DomainOprationEnum oprationEnum = DomainOprationEnum.PAYMENT_WAIT_RISK_AUDIT;
        PaymentAccountStatusEnum historyStatus = PaymentAccountStatusEnum.of(paymentAccount.getStatusId());
        DomainStatus.getInstance().check(oprationEnum, paymentAccount.getStatusId());

        PaymentAccountStatusEnum statusEnum = PaymentAccountStatusEnum.WAIT_SW_EXAMINE;//待商务审核
        paymentAccount.setStatusId(statusEnum.getCode());

        if(isPass){//通过
            super.updateById(paymentAccount);
        }else{//不通过
            //还原汇率
            adjustRateRecalculate(paymentAccount,paymentAccount.getOldExchangeRate());
        }

        //记录历史状态
        statusHistoryService.saveHistory(oprationEnum,
                paymentAccount.getId().toString(), PaymentAccount.class.getName(),
                historyStatus.getCode(),historyStatus.getName(),
                statusEnum.getCode(),statusEnum.getName(),StringUtils.removeSpecialSymbol(memo));

        //发送任务通知
        TaskNoticeContentDTO taskNoticeContentDTO = new TaskNoticeContentDTO();
        taskNoticeContentDTO.setDocumentType(DomainTypeEnum.PAYMENTACCOUNT.getCode());
        taskNoticeContentDTO.setDocumentId(paymentAccount.getId().toString());
        taskNoticeContentDTO.setCustomerId(paymentAccount.getCustomerId());
        taskNoticeContentDTO.setContent(String.format("付汇单【%s】已完成风控审批，请您尽快审核。", paymentAccount.getDocNo()));
        taskNoticeContentDTO.setOperationCode(DomainOprationEnum.PAYMENT_ACCOUNT_AUDIT.getCode());
        taskNoticeContentDTO.setQueryParamsMap(ImmutableMap.of("docNo",paymentAccount.getDocNo()));
        taskNoticeContentService.add(taskNoticeContentDTO);
    }

    @Override
    @Transactional(rollbackFor = Exception.class)
    public void completeCostRisk(Long paymentId, Boolean isPass, String memo) {
        PaymentAccount paymentAccount = super.getById(paymentId);
        TsfPreconditions.checkArgument(Objects.nonNull(paymentAccount),new ServiceException("付汇单不存在"));
        DomainOprationEnum oprationEnum = DomainOprationEnum.PAYMENT_WAIT_RISK_AUDIT;
        PaymentAccountStatusEnum historyStatus = PaymentAccountStatusEnum.of(paymentAccount.getStatusId());
        DomainStatus.getInstance().check(oprationEnum, paymentAccount.getStatusId());

        PaymentAccountStatusEnum statusEnum = PaymentAccountStatusEnum.WAIT_SW_EXAMINE;//待商务审核
        paymentAccount.setStatusId(statusEnum.getCode());
        super.updateById(paymentAccount);

        //记录历史状态
        statusHistoryService.saveHistory(oprationEnum,
                paymentAccount.getId().toString(), PaymentAccount.class.getName(),
                historyStatus.getCode(),historyStatus.getName(),
                statusEnum.getCode(),statusEnum.getName(),StringUtils.removeSpecialSymbol(memo));

        //发送任务通知
        TaskNoticeContentDTO taskNoticeContentDTO = new TaskNoticeContentDTO();
        taskNoticeContentDTO.setDocumentType(DomainTypeEnum.PAYMENTACCOUNT.getCode());
        taskNoticeContentDTO.setDocumentId(paymentAccount.getId().toString());
        taskNoticeContentDTO.setCustomerId(paymentAccount.getCustomerId());
        taskNoticeContentDTO.setContent(String.format("付汇单【%s】已完成风控审批，请您尽快审核。", paymentAccount.getDocNo()));
        taskNoticeContentDTO.setOperationCode(DomainOprationEnum.PAYMENT_ACCOUNT_AUDIT.getCode());
        taskNoticeContentDTO.setQueryParamsMap(ImmutableMap.of("docNo",paymentAccount.getDocNo()));
        taskNoticeContentService.add(taskNoticeContentDTO);
    }

    @Override
    @Transactional(rollbackFor = Exception.class)
    public void saveConfirmBank(ConfirmBankDTO dto) {
        PaymentAccount paymentAccount = super.getById(dto.getId());
        TsfPreconditions.checkArgument(Objects.nonNull(paymentAccount),new ServiceException("付汇单不存在"));
        DomainOprationEnum oprationEnum = DomainOprationEnum.PAYMENT_WAIT_CONFIRM_BANK;
        PaymentAccountStatusEnum historyStatus = PaymentAccountStatusEnum.of(paymentAccount.getStatusId());
        DomainStatus.getInstance().check(oprationEnum, paymentAccount.getStatusId());

        PaymentAccountStatusEnum statusEnum = PaymentAccountStatusEnum.WAIT_PAY;//待付汇
        paymentAccount.setStatusId(statusEnum.getCode());
        SubjectOverseas overseas = subjectOverseasService.getById(dto.getPayerId());
        TsfPreconditions.checkArgument(Objects.nonNull(overseas),new ServiceException("付款方不存在"));
        SubjectOverseasBank overseasBank = subjectOverseasBankService.getById(dto.getPayerBankId());
        TsfPreconditions.checkArgument(Objects.nonNull(overseasBank),new ServiceException("付款银行不存在"));
        paymentAccount.setPayer(overseas.getName());
        paymentAccount.setPayerBankName(overseasBank.getName());
        paymentAccount.setPayerBankAccountNo(overseasBank.getAccount());
        super.updateById(paymentAccount);

        //记录历史状态
        statusHistoryService.saveHistory(oprationEnum,
                paymentAccount.getId().toString(), PaymentAccount.class.getName(),
                historyStatus.getCode(),historyStatus.getName(),
                statusEnum.getCode(),statusEnum.getName(),"确定付款银行");

        //发送任务通知
        TaskNoticeContentDTO taskNoticeContentDTO = new TaskNoticeContentDTO();
        taskNoticeContentDTO.setDocumentType(DomainTypeEnum.PAYMENTACCOUNT.getCode());
        taskNoticeContentDTO.setDocumentId(paymentAccount.getId().toString());
        taskNoticeContentDTO.setCustomerId(paymentAccount.getCustomerId());
        taskNoticeContentDTO.setContent(String.format("付汇单【%s】已确定付款行，请尽快完成付汇。", paymentAccount.getDocNo()));
        taskNoticeContentDTO.setOperationCode(DomainOprationEnum.PAYMENT_CONFIRM_PAY.getCode());
        taskNoticeContentDTO.setQueryParamsMap(ImmutableMap.of("docNo",paymentAccount.getDocNo()));
        taskNoticeContentService.add(taskNoticeContentDTO);
    }

    @Override
    @Transactional(rollbackFor = Exception.class)
    public void paymentCompleted(PaymentCompletedDTO dto) {
        PaymentAccount paymentAccount = super.getById(dto.getId());
        TsfPreconditions.checkArgument(Objects.nonNull(paymentAccount),new ServiceException("付汇单不存在"));
        DomainOprationEnum oprationEnum = DomainOprationEnum.PAYMENT_CONFIRM_PAY;
        PaymentAccountStatusEnum historyStatus = PaymentAccountStatusEnum.of(paymentAccount.getStatusId());
        DomainStatus.getInstance().check(oprationEnum, paymentAccount.getStatusId());

        PaymentAccountStatusEnum statusEnum = PaymentAccountStatusEnum.COMPLETED;//已完成
        paymentAccount.setStatusId(statusEnum.getCode());
        //校验赋值币制信息
        Currency currency = checkWrapCurrency(dto.getActualCurrencyName());
        paymentAccount.setActualCurrencyId(currency.getId());
        paymentAccount.setActualCurrencyName(currency.getName());
        paymentAccount.setActualAccountValue(dto.getActualAccountValue());

        super.updateById(paymentAccount);

        //记录历史状态
        statusHistoryService.saveHistory(oprationEnum,
                paymentAccount.getId().toString(), PaymentAccount.class.getName(),
                historyStatus.getCode(),historyStatus.getName(),
                statusEnum.getCode(),statusEnum.getName(),"付汇完成");

        //获取付汇的订单
        List<PaymentAccountMember> paymentAccountMembers = paymentAccountMemberService.getByPaymentAccountId(paymentAccount.getId());
        List<String> orderNos = paymentAccountMembers.stream().map(PaymentAccountMember::getImpOrderNo).collect(Collectors.toList());
        //发送短信通知客户
        LinkedHashMap<String,String> paramsMap = new LinkedHashMap<>();
        if(CollUtil.isNotEmpty(orderNos) && orderNos.size() > 2) {
            orderNos = orderNos.stream().limit(2).collect(Collectors.toList());
        }
        paramsMap.put("orderNos", CollUtil.join(orderNos, ","));
        SmsNoticeMessageDTO smsNoticeMessageDTO = new SmsNoticeMessageDTO();
        smsNoticeMessageDTO.setMessageNodeEnum(MessageNodeEnum.PAY_COMPLETE);
        Person person = personService.getPhoneByCustomerId(paymentAccount.getCustomerId());
        smsNoticeMessageDTO.setPhoneNo(Objects.nonNull(person) ? person.getPhone() : "");
        smsNoticeMessageDTO.setParams(paramsMap);
        shortMessageService.sendNoticeMessage(smsNoticeMessageDTO);

        //发送微信通知
        WxNoticeMessageDTO wxNoticeMessageDTO = new WxNoticeMessageDTO();
        wxNoticeMessageDTO.setWxgzhOpenid(Objects.nonNull(person) ? person.getWxgzhOpenid() : "");
        wxNoticeMessageDTO.setWxMessageDefineTemplate(WxMessageDefineTemplate.PAY_COMPLETE);
        wxNoticeMessageDTO.setIsNavToMiniprogram(Boolean.TRUE);
        wxNoticeMessageDTO.setMiniprogram(new WxMiniProgram("pages/finance/payment/info?id=" + paymentAccount.getId()));
        LinkedHashMap<String, WxNoticeAttr> params = new LinkedHashMap<>();
        params.put("first",new WxNoticeAttr("尊敬的客户：您好，您有一笔付汇单据已完成付汇"));
        params.put("keyword1",new WxNoticeAttr(paymentAccount.getDocNo(),"#9C9C9C"));
        params.put("keyword2",new WxNoticeAttr(CollUtil.join(orderNos, ","),"#9C9C9C"));
        params.put("keyword3",new WxNoticeAttr(StringUtils.formatCurrency(paymentAccount.getAccountValue()) + " " + paymentAccount.getCurrencyName()));
        params.put("keyword4",new WxNoticeAttr(LocalDateTimeUtils.formatTime(paymentAccount.getDateCreated(),"yyyy-MM-dd HH:mm"),"#9C9C9C"));
        params.put("keyword5",new WxNoticeAttr(StringUtils.formatCurrency(paymentAccount.getBankFee()),"#9C9C9C"));
        params.put("remark",new WxNoticeAttr("点击查看付汇详情"));
        wxNoticeMessageDTO.setParams(params);
        wxMessageService.sendWxNotice(wxNoticeMessageDTO);

        //清空付款单任务通知
        clearTaskNotice(paymentAccount.getId());
    }

    @Override
    public Map<String, Object> getPaymentAmount(Long orderId, String statusId) {
        return paymentAccountMapper.getPaymentAmount(orderId,statusId);
    }

    @Override
    public PageInfo<ClientPaymentListVO> clientPageList(ClientPaymentApplyQTO qto) {
        PageHelper.startPage(qto.getPage(),qto.getLimit());
        qto.setCustomerId(SecurityUtil.getCurrentCustomerId());
        if(StringUtils.isNotEmpty(qto.getClientStatusId()) && ClientStatusMappingUtil.PAY_CLIENT_STATUS_MAPPING.containsKey(qto.getClientStatusId())) {
            Map<String, ClientInfoStatus> clientStatusMapping = ClientStatusMappingUtil.PAY_CLIENT_STATUS_MAPPING;
            qto.setStatusIds(clientStatusMapping.get(qto.getClientStatusId()).getBackStatusId());
        }
        List<ClientPaymentListVO> dataList = paymentAccountMapper.clientList(qto);
        return new PageInfo<>(dataList);
    }

    @Override
    public Map<String, Long> getClientPaymentNum( ) {
        //此处把客户端客户付汇状态与后台付汇状态映射
        Map<String,Long> clientPayStatusStatistic = Maps.newHashMap();
        Map<String, ClientInfoStatus> clientStatusMapping = ClientStatusMappingUtil.PAY_CLIENT_STATUS_MAPPING;
        clientStatusMapping.forEach((k,v)->{
            clientPayStatusStatistic.put(k,paymentAccountMapper.countByCustomerAndStatusIds(SecurityUtil.getCurrentCustomerId(),v.getBackStatusId()));
        });
        return clientPayStatusStatistic;
    }

    @Override
    public ClientPaymentDetailPlusVO clientPaymentInfo(Long id) {
        PaymentAccount paymentAccount = super.getById(id);
        TsfPreconditions.checkArgument(Objects.nonNull(paymentAccount),new ServiceException("付汇申请信息不存在"));
        PermissionUtil.checkClientCustomerPermission(paymentAccount);
        List<PaymentAccountMemberVO> members = paymentAccountMemberService.findByPaymentAccountId(id);
        PaymentAccountVO paymentAccountVO = beanMapper.map(paymentAccount,PaymentAccountVO.class);
        return new ClientPaymentDetailPlusVO(beanMapper.map(paymentAccount, ClientPaymentDetailVO.class),members);
    }

    @Transactional(rollbackFor = Exception.class)
    @Override
    public void clientApply(PaymentApplyDTO dto) {
        apply(dto);
    }
    // 手续费收取规则配置
    private final static Map<String,BigDecimal> feeMap = new LinkedMap<String,BigDecimal>(){{
        put("OUR_OU",BigDecimal.valueOf(35.00));//客户承担-境外
        put("OUR_IN",BigDecimal.valueOf(7.11));//客户承担-本地
        put("SHA_OU",BigDecimal.valueOf(15.00));//共同承担-境外
        put("SHA_IN",BigDecimal.valueOf(7.11));//共同承担-本地
        put("BEN_OU",BigDecimal.valueOf(0));//供应商承担-境外
        put("BEN_IN",BigDecimal.valueOf(0));//供应商承担-本地
    }};
    //汇丰本地银行
    private final static List<String> notAccept = Arrays.asList("HSBCHK");

    @Override
    public BigDecimal calculationFee(Long orderId,String paymentTerm, String counterFeeType, String swiftCode) {
        BigDecimal bankFee = BigDecimal.ZERO;
        if(Objects.equals(PaymentTermEnum.TT,PaymentTermEnum.of(paymentTerm))){
            if(StringUtils.isEmpty(counterFeeType)){
                counterFeeType = "OUR";
            }
            if(StringUtils.isEmpty(swiftCode)||swiftCode.length()<5){
                swiftCode = "00000";
            }
            swiftCode = swiftCode.toUpperCase();
            for(int i=0;i<notAccept.size();i++){
                if(swiftCode.indexOf(notAccept.get(i))!=-1){
                    return BigDecimal.ZERO;
                }
            }
            bankFee = feeMap.get(String.format("%s_%s",counterFeeType,swiftCode.substring(4).startsWith("HK")?"IN":"OU"));
        }
        //转换人民币
        if(bankFee.compareTo(BigDecimal.ZERO)==1){
            //获取美金汇率
            BigDecimal chinaRate = DataCalling.getInstance().obtainBankChinaRate("USD", RateTypeEnum.PAY_BANK_CHINA_SELL.getCode(),DateUtils.getCurrDate().concat(" 09:30:00"));
            if(Objects.isNull(chinaRate)){
                throw new ServiceException("9:30 美金汇率暂未公布请稍后再试");
            }
            ImpOrder impOrder = impOrderService.getById(orderId);
            if(Objects.isNull(impOrder)){
                throw new ServiceException("请选择一个有效订单进行付汇");
            }
            //根据业务类型获取税点
            BigDecimal taxPoint = DataCalling.getInstance().obtainTaxPoint(BusinessTypeEnum.of(impOrder.getBusinessType()));
            bankFee = bankFee.multiply(chinaRate).multiply(BigDecimal.ONE.add(taxPoint)).setScale(2,BigDecimal.ROUND_HALF_UP);
        }
        return bankFee;
    }

    @Override
    public Long createEntrustExcel(Long id) {
        PaymentAccount paymentAccount = super.getById(id);
        Optional.ofNullable(paymentAccount).orElseThrow(()->new ServiceException("付汇单不存在"));
        String path = filePath + "temp/"+"scm"+"/pay_entrust/";
        String sysFileName = String.format("%s_%s.xls",paymentAccount.getDocNo(),LocalDateTimeUtils.formatNow("yyyyMMddHHmmssSSS"));
        ExportFileDTO exportFileDTO = createExcel(paymentAccount,path,sysFileName);
        Long fileId = exportFileService.save(exportFileDTO);
        return fileId;
    }

    @Override
    public void removeAllById(Long id) {
        //已作废的数据才允许删除
        PaymentAccount paymentAccount = super.getById(id);
        if(Objects.isNull(paymentAccount) || !Objects.equals(PaymentAccountStatusEnum.TO_VOID,PaymentAccountStatusEnum.of(paymentAccount.getStatusId()))){
            throw new ServiceException("已作废的单据才允许删除");
        }
        //删除历史状态
        statusHistoryService.removeHistory(id.toString(), PaymentAccount.class.getName());
        //删除单据
        super.removeById(id);
        //清空任务
        clearTaskNotice(id);
    }

    @Override
    public List<BigDecimal> getExchangeRateByImpOrderNo(String impOrderNo) {
        return paymentAccountMapper.getExchangeRateByImpOrderNo(impOrderNo);
    }

    public ExportFileDTO createExcel(PaymentAccount paymentAccount, String path, String sysFileName) {
        try {
            Customer customer = customerService.getById(paymentAccount.getCustomerId());
            String fileName = String.format("%s_pay_entrust.xls",paymentAccount.getDocNo());
            new File(path).mkdirs();

            WritableWorkbook wwb = Workbook.createWorkbook(new File(path+sysFileName));
            WritableFont font18 = new WritableFont(WritableFont.ARIAL, 18, WritableFont.NO_BOLD, false);
            WritableFont font12 = new WritableFont(WritableFont.ARIAL, 10, WritableFont.NO_BOLD, false);
            WritableCellFormat titleBold = new WritableCellFormat(font18);
            titleBold.setAlignment(Alignment.CENTRE);
            titleBold.setVerticalAlignment(VerticalAlignment.CENTRE);
            WritableCellFormat bold = new WritableCellFormat(font12);
            bold.setVerticalAlignment(VerticalAlignment.CENTRE);
            bold.setWrap(true);
            WritableCellFormat boldWrap = new WritableCellFormat(font12);
            boldWrap.setVerticalAlignment(VerticalAlignment.CENTRE);
            boldWrap.setWrap(true);
            WritableCellFormat cenBold = new WritableCellFormat(font12);
            cenBold.setAlignment(Alignment.CENTRE);
            cenBold.setVerticalAlignment(VerticalAlignment.CENTRE);
            WritableCellFormat boldBorder = new WritableCellFormat(font12);
            boldBorder.setBorder(jxl.format.Border.ALL, jxl.format.BorderLineStyle.THIN, jxl.format.Colour.BLACK);
            boldBorder.setWrap(false);
            boldBorder.setVerticalAlignment(VerticalAlignment.CENTRE);
            WritableCellFormat cenBoldBorder = new WritableCellFormat(font12);
            cenBoldBorder.setAlignment(Alignment.CENTRE);
            cenBoldBorder.setBorder(jxl.format.Border.ALL, jxl.format.BorderLineStyle.THIN, jxl.format.Colour.BLACK);
            cenBoldBorder.setWrap(true);
            cenBoldBorder.setVerticalAlignment(VerticalAlignment.CENTRE);
            WritableSheet sheet1 = wwb.createSheet("付汇委托确认书", 0);
            Integer row = 0;
            sheet1.setColumnView(0, 4);
            sheet1.setColumnView(1, 14);
            sheet1.setColumnView(2, 24);
            sheet1.setColumnView(3, 10);
            sheet1.setColumnView(4, 10);
            sheet1.setColumnView(5, 30);

            sheet1.mergeCells(0, row, 5, row);
            sheet1.addCell(new Label(0, row++, "付汇委托确认书", titleBold));

            sheet1.mergeCells(0, row, 1, row);
            sheet1.addCell(new Label(0, row, "编号：", bold));
            sheet1.addCell(new Label(2, row, paymentAccount.getDocNo(), bold));
            sheet1.addCell(new Label(4, row, "日期：", bold));
            sheet1.addCell(new Label(5, row++, StringUtils.null2EmptyWithTrim(LocalDateTimeUtils.formatShort(paymentAccount.getAccountDate())), bold));

            sheet1.mergeCells(0, row, 1, row);
            sheet1.addCell(new Label(0, row, "委托方：", bold));
            sheet1.addCell(new Label(2, row, customer.getName(), bold));
            sheet1.addCell(new Label(4, row, "代理方：", bold));
            sheet1.addCell(new Label(5, row++, StringUtils.null2EmptyWithTrim(paymentAccount.getPayer()), bold));
            row++;

            sheet1.mergeCells(0, row, 1, row);
            sheet1.addCell(new Label(0, row, "供应商名称：", boldBorder));
            sheet1.mergeCells(2, row, 5, row);
            sheet1.setRowView(row,320);
            sheet1.addCell(new Label(2, row++, StringUtils.null2EmptyWithTrim(paymentAccount.getPayee()), boldBorder));
            sheet1.mergeCells(0, row, 1, row);
            sheet1.addCell(new Label(0, row, "要求付汇日期：", boldBorder));
            sheet1.mergeCells(2, row, 5, row);
            sheet1.setRowView(row,320);
            sheet1.addCell(new Label(2, row++, StringUtils.null2EmptyWithTrim(LocalDateTimeUtils.formatShort(paymentAccount.getAccountDate())), boldBorder));
            sheet1.mergeCells(0, row, 1, row);
            sheet1.addCell(new Label(0, row, "付汇金额：", boldBorder));
            sheet1.mergeCells(2, row, 5, row);
            sheet1.setRowView(row,320);
            sheet1.addCell(new Label(2, row++, "(" + paymentAccount.getCurrencyId() + ")" + StringUtils.formatCurrency(paymentAccount.getAccountValue()), boldBorder));
            sheet1.mergeCells(0, row, 1, row);
            sheet1.setRowView(row,320);
            sheet1.addCell(new Label(0, row, "付汇金额：", boldBorder));
            sheet1.mergeCells(2, row, 5, row);
            sheet1.setRowView(row,320);
            sheet1.addCell(new Label(2, row++, String.format("(CNY)%s",StringUtils.formatCurrency(paymentAccount.getAccountValueCny())), boldBorder));
            sheet1.mergeCells(0, row, 0, row+5);
            sheet1.addCell(new Label(0, row, "收\r\n款\r\n人\r\n银\r\n行\r\n资\r\n料", cenBoldBorder));
            sheet1.addCell(new Label(1, row, "公司全称：", boldBorder));
            sheet1.mergeCells(2, row, 5, row);
            sheet1.setRowView(row,320);
            sheet1.addCell(new Label(2, row++, StringUtils.null2EmptyWithTrim(paymentAccount.getPayee()), boldBorder));
            sheet1.addCell(new Label(1, row, "银行名称：", boldBorder));
            sheet1.mergeCells(2, row, 5, row);
            sheet1.setRowView(row,320);
            sheet1.addCell(new Label(2, row++, StringUtils.null2EmptyWithTrim(paymentAccount.getPayeeBankName()), boldBorder));
            sheet1.addCell(new Label(1, row, "银行地址：", boldBorder));
            sheet1.mergeCells(2, row, 5, row);
            sheet1.setRowView(row,320);
            sheet1.addCell(new Label(2, row++, StringUtils.null2EmptyWithTrim(paymentAccount.getPayeeBankAddress()), boldBorder));
            sheet1.addCell(new Label(1, row, "银行帐号：", boldBorder));
            sheet1.mergeCells(2, row, 5, row);
            sheet1.setRowView(row,320);
            sheet1.addCell(new Label(2, row++, StringUtils.null2EmptyWithTrim(paymentAccount.getPayeeBankAccountNo()), boldBorder));
            sheet1.addCell(new Label(1, row, "银行代码：", boldBorder));
            sheet1.mergeCells(2, row, 5, row);
            sheet1.setRowView(row,320);
            sheet1.addCell(new Label(2, row++, StringUtils.null2EmptyWithTrim(paymentAccount.getSwiftCode()), boldBorder));
            sheet1.addCell(new Label(1, row, "其他相关资料：", boldBorder));
            sheet1.mergeCells(2, row, 5, row);
            sheet1.setRowView(row,320);
            sheet1.addCell(new Label(2, row++, "", boldBorder));
            row++;
            sheet1.mergeCells(4, row, 5, row);
            sheet1.addCell(new Label(4, row, "委托方签字并盖章确认：", bold));
            wwb.write();
            wwb.close();

            ExportFileDTO exportFileDTO = new ExportFileDTO();
            exportFileDTO.setPath(path+"/"+sysFileName);
            exportFileDTO.setFileName(fileName);
            exportFileDTO.setOnce(Boolean.TRUE);
            exportFileDTO.setOperator("系统自动生成");
            return exportFileDTO;
        } catch (Exception e) {
            log.error(String.format("付汇单【%s】生成付汇委托书EXCEL失败：",paymentAccount.getDocNo()),e);
            throw new ServiceException("导出付汇委托书EXCEL失败，请您稍后再试");
        }
    }

    public Currency checkWrapCurrency(String currencyName) {
        TsfPreconditions.checkArgument(StringUtils.isNotEmpty(currencyName),new ServiceException("币制不能为空"));
        Currency currency = currencyService.findByName(currencyName);
        TsfPreconditions.checkArgument(Objects.nonNull(currency),new ServiceException(String.format("【%s】币制不存在",currencyName)));
        return currency;
    }
}
